14.1 代码块
说明:
代码块是由C语言实现的,是对C语言中函数的扩展。
支持的语言:Objective-C、C、C++、Objective-C++
用途:替代函数或实现闭包
现状:代码块在Xcode的GCC和CLang工具中是有效的,但它不属于ANSI的C语言标准。关于代码块的提议已经提交给C语言标准团体。
14.1.1 代码块和函数指针
说明:
代码块的语法借鉴了函数指针
- 返回类型可以手动声明也可以由编译器通过代码块推导
- 具有指定类型的参数列表
- 拥有名称
- 代码放在
{}中语法:
<returntype> (^blockname)(list of arguments) = ^(arguments){body;}
实现部分推导出返回值类型
1 | // 实现部分省略了返回值类型,没有参数列表 |
1 | /** |
14.1.1.1 通过代码块名调用代码块
说明:可以像调用
函数一样调用代码块。
比函数强大:代码块可以访问与它相同的有效范围内声明的变量。
1 | // 声明并初始化变量:声明时的作用域和代码块相同 |
14.1.1.2 直接调用代码块(匿名)
说明:使用代码块时通常不需要创建一个代码块变量,而是在代码中内联代码块的内容。
使用场景:作为参数传递给方法或函数
1 | // 数组 |
14.1.1.3 使用typedef关键字
说明:将
代码块声明定义为一种类型,更易于代码的编写。
语法:typedef 代码块定义;
注意:typedef后面的代码块定义中的代码块名不再具备代码块名的功能,而是一种类型名。
1 | // 将代码块定义为一种类型: MKSampleMultiply2BlockRef |
14.1.1.4 代码块和变量
说明:代码块被声明后会捕捉到创建时的上下文中的变量或函数。
- 全局变量(包括在封闭范围内声明的本地静态变量)
- 全局函数
- 封闭范围内的参数
Objective-C的实例变量- 代码块内部的本地变量
本地变量
说明:与代码块在同一范围内声明的变量。
捕获情况:代码块会在定义时把本地变量当作常量复制并保存它们的状态。
1 | // 定义代码块类型 |
全局变量
说明:可以根据需要将变量标记为
静态的(全局的)。
捕获情况:同本地变量。
1 | static double a = 10, b = 20; |
参数变量
说明:代码块中的参数变量与函数中的参数变量具有相同的作用。
1 | // 定义代码块类型 |
_block变量
关键字:
_block
说明:本地变量会被代码块当作常量获取到,如果想要修改它们的值,必须通过_black将它们声明为可修改的。
限制:由两种情况不能使用_block修饰
- 长度可变数组
- 包含长度可变数组的结构体
1 | // 用_block 修饰,使变量 c 在代码块中的副本可修改 |
代码块内部的本地变量
说明:对
代码块来说,和本地变量一样使用。
1 | // 定义并实现代码块 |
14.1.2 Objective-C代码块内存管理
说明:代码块是对象,所以可以向它发送任何与内存管理由关的信息。
- 如果引用了一个
Objective-C对象,必须要保留它- 如果类的方法中的
代码块通过引用访问了一个实例变量,要保留一次self(执行所在方法的对象)- 如果通过数值访问了一个实例变量,变量需要
保留
ProcessString.h:方法中包含代码块的类
1 |
|
ProcessString.m
1 |
|
main.m
1 |
|
扩展:在C语言中,必须使用
Block_copy()和Block_release()函数来适当地管理内存。
1 |
|
14.2 并发性
说明:能够在同一时间执行多个任务的程序称为
并发的(concurrent)程序。苹果公司提供了多种可以利用多核特性的API。
| 相关技术选择 | 说明 | 备注 |
|---|---|---|
| POSIX线程 | 利用并发行最基础的方法是使用POSIX线程来处理程序的不同部分使其能够独立运行。POSIX线程拥有支持C和Objective-C的API。 |
因为线程是级别较低的API,必须手动管理,挑战很大 |
| GDC(Grand Central Dipatch) | 运行在系统级别,减少了不少线程管理的麻烦 | 可以平衡应用程序所有内同的家在,从而提高计算机或设备的运行效率 |
14.2.1 同步
关键字:
@synchronized
说明:用来设置临界区,确保多个线程不会在同一时间进入临界区。
相关:@property指令的atomic特性会让编译器通过插入@synchronize(mutex, atomic)生成强制彼此互斥的getter和setter方法(降低了代码性能),而nonatomic特性(默认)则不会。
14.2.1.1 选择性能
说明:
NSObject提供了一些可以使代码在后台以较低性能运行的方法(方法名带有performSelector前缀)
performSelectorInBackground实例方法
说明:通过创建一个线程,在后端运行一个指定的方法。
限制:指定运行的方法(第一个参数)要遵从以下限制
- 方法中需要
@autoreleasepool- 方法不能有返回值,参数最多一个且必须为
id类型
- (void) myMethod;- (void) myMethod:(id)myObject;原型:
NSObject
1 | /** |
SelectorTester.h
1 |
|
SelectorTester.m
1 |
|
14.2.1.2 调度队列
说明:
GDC可以使用调度队列(dispatch queue),共有3种。
| 队列类型 | 说明 | 并行/串行 | 备注 |
|---|---|---|---|
| 连续队列 | 根据指派的顺序执行任务 | 串行,先入先出(FIFO,栈) | 可以创建多个连续队列,彼此并行 |
| 并发队列 | 并发执行一个或多个任务 | 并行,根据指派到队列的顺序开始执行 | 无法创建,只能从系统提供的并发队列中选择(一共3个) |
| 主队列 | 应用程序的有效的主队列 | 主线程只有一个,无所谓串/并行 | 执行的应用程序的主线程任务 |
调度队列数据类型:
dispatch_queue_t
连续队列
说明:只要任务是异步提交的,队列会确保任务根据预定顺序执行,不会发生死锁。
适用:一连串的任务需要按照一定的顺序执行的场景
dispatch_queue_create全局方法
说明:创建连续队列
原型:
1 | /** |
1 | // 声明连续队列 |
并发队列
说明:并发调度队列适用于那些可以并行运行的任务
- 开始执行时间遵从FIFO
- 任务可以在前一个任务结束前就开始执行
- 一次所运行的任务数量是无法预测的(根据其它运行的任务的状况)
技巧:如果需要确保每次运行的任务的数量都是一样的,可以通过线程
API来手动管理线程。
dispatch_get_global_queue全局方法
说明:获取系统的并发队列。
原型:/usr/include/dispatch/queue.h
1 | /** |
1 | // 声明并发队列 |
主队列
dispatch_get_current_queue全局方法
说明:获取
当前运行的队列代码块,如果在代码块的对象之外调用了这个函数,则它会返回主队列。
注意:该方法在从OS X 10.9和ios 6开始被废弃,因为GCD队列本身是不可重入的,同步阻塞会导致死锁。
用途:仍然可以作为调试手段在代码中使用。
1 | // 获取主线程或当前队列 |
14.2.2 队列也要内存管理
说明:
调度队列是引用计数对象,可以使用dispatch_retain()和dispatch_release()来修改队列的保留计数器的值。
14.2.2.1 队列的上下文
说明:可以向
调度对象(包括调度队列)指派全局数据上下文,可以在上下文中指派任意类型的数据,比如Objective-C对象或指针。
内存管理:必须在需要队列上下文的时候分配内存并在队列销毁之前进行清理。
dispatch_set_context全局方法
说明:为指定队列设置
全局上下文。
原型:/usr/include/dispatch/object.h
1 | /** |
dispatch_get_context全局方法
说明:获得
调度队列的全局数据上下文。
原型:/usr/include/dispatch/object.h
1 | /** |
1 | // 创建可变字典(作为全局数据上下文) |
14.2.2.2 全局数据上下文内存管理
说明:编写一个
终结器(finalizer)函数,在dealloc中调用。
1 | /** |
14.2.2.3 向调度队列添加任务
说明:有两种方式可以向队列中添加任务,每种方式针对
代码块和函数各有一个调度函数(共4个)
- 同步:队列会一直等待前面任务结束
- 异步:添加任务后,不必等待任务,函数会立刻返回(推荐,因为不会阻塞其他代码的运行)
|**|同步|异步|
|代码块|dispatch_sync|dispatch_async|
|函数|dispatch_sync_f|dispatch_async_f|
dispatch_sync全局函数
说明:向队列中
同步添加代码块。
原型:/use/include/dispatch/queue.h
1 | /** |
dispatch_async全局函数
说明:向队列中
异步添加代码块。
原型:/use/include/dispatch/queue.h
1 | /** |
1 | // 异步添加代码块:内联方式 |
dispatch_sync_f全局函数
说明:向队列中
同步添加代码块。
原型:/use/include/dispatch/queue.h
1 | /** |
dispatch_async_f全局函数
说明:向队列中
异步添加代码块。
原型:/use/include/dispatch/queue.h
1 | /** |
1 | // 定义好要添加到队列的函数 |
1 | // 同步向队列中添加函数 |
14.2.2.3 调度队列的暂停和重启
dispatch_suspend全局方法
说明:暂停队列
原型:/use/include/dispatch/object.h
1 | /** |
dispatch_resume全局方法
说明:重启队列
原型:/use/include/dispatch/object.h
1 | /** |
1 | // 暂停队列 |
14.2.3 操作队列
说明:有一些称为
操作(operation)的API,可以让队列使用起来更加简单。
- 创建一个
操作对象- 将其指派给
操作队列操作被队列执行
操作的创建方式
说明:一共有3种方式
| 方式 | 说明 |
|---|---|
调用操作(NSInvocationOperation) |
前提是已经拥有一个可以完成工作的类,并且想在队列上执行 |
代码块操作(NSBlockOperation) |
类似包含了要执行代码块的dispatch_async函数 |
自定义的操作 |
通过继承NSOperation定义自己的操作 |
方式一:创建调用操作
1 | @implementation MyCustomClass |
方式二:创建代码块操作
说明:创建时作为参数的
代码块的类型和在调度队列中使用的相同。
- 一旦创建了第一个
代码块操作,便可以通过addExecutionBlock方法继续添加更多的代码块- 根据
队列的类型,代码块会分别以连续或并行的方式运行
1 | NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock: ^ { |
向队列中添加操作
说明:可以使用
NSOperationQueue来取代之前使用的dispatch_queue函数,特点如下
- 并发执行
操作- 具有
相关性,也就是说,如果某个操作是基于其它操作的,则也会先被执行技巧:如果要确保添加的
操作是连续执行(串行)的,可以设置最大并发操作数为1,这样会按照先入先出的规范执行
1 | // 获取队列 |